/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sv_parse.cpp - client message parsing
//


#include "sv_local.h"


/*
 ==============================================================================

 CLIENT COMMAND EXECUTION

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
static void SV_UserInfoCommand (client_t *client){

}

/*
 ==================
 SV_DisconnectCommand
 ==================
*/
static void SV_DisconnectCommand (client_t *client){

	SV_DropClient(client, "Disconnected from server");
}

/*
 ==================
 
 ==================
*/
static void SV_StartDownloadCommand (client_t *client){

}

/*
 ==================
 
 ==================
*/
static void SV_StopDownloadCommand (client_t *client){

}

/*
 ==================
 
 ==================
*/
static void SV_NextDownloadCommand (client_t *client){

}

/*
 ==================
 
 ==================
*/
static void SV_VerifyPureCommand (client_t *client){

	// Ignore it if we're getting remaining parasite pure verification requests
	if (client->serverId != sv.serverId){
		Com_DPrintf(S_COLOR_YELLOW "Ignoring outdated pure verification request from %s\n", client->playerName);
		return;
	}

	// TODO: check pack

	// Can now enter the game
	client->pureAuthentic = true;

	client->serverStateMessage = -1;

	// Start sending the game state immediately
	client->gameStateMessage = -1;
	client->gameStateOffset = 0;
}

/*
 ==================
 SV_GameStateCommand
 ==================
*/
static void SV_GameStateCommand (client_t *client){

	// Ignore it if we're getting remaining parasite game state requests
	if (client->serverId != sv.serverId){
		Com_DPrintf(S_COLOR_YELLOW "Ignoring outdated game state request from %s\n", client->playerName);
		return;
	}

	// Parse the offset
	client->gameStateOffset = Str_ToInteger(Cmd_Argv(1));
}

/*
 ==================
 SV_ExecuteClientCommand
 ==================
*/
static bool SV_ExecuteClientCommand (client_t *client, const char *command, bool allowGameCommand){

	const char	*cmd;

	Com_DPrintf("Client command from %s" S_COLOR_DEFAULT ": %s\n", client->playerName, command);

	// Execute the client command
	Cmd_TokenizeString(command);

	if (!Cmd_Argc())
		return true;	// No tokens

	cmd = Cmd_Argv(0);

	// Check for a server level command
	if (!Str_ICompare(cmd, "userInfo")){
		SV_UserInfoCommand(client);
		return true;
	}

	if (!Str_ICompare(cmd, "disconnect")){
		SV_DisconnectCommand(client);
		return true;
	}

	if (!Str_ICompare(cmd, "startDownload")){
		SV_StartDownloadCommand(client);
		return true;
	}

	if (!Str_ICompare(cmd, "stopDownload")){
		SV_StopDownloadCommand(client);
		return true;
	}

	if (!Str_ICompare(cmd, "nextDownload")){
		SV_NextDownloadCommand(client);
		return true;
	}

	if (!Str_ICompare(cmd, "verifyPure")){
		SV_VerifyPureCommand(client);
		return true;
	}

	if (!Str_ICompare(cmd, "gameState")){
		SV_GameStateCommand(client);
		return true;
	}

	// Pass unknown client commands to the game module if allowed
	if (allowGameCommand){
		glqSGame->ClientCommand(client->clientNum);
		return true;
	}

	Com_DPrintf("Client command from %s" S_COLOR_DEFAULT " ignored: %s\n", client->playerName, cmd);

	return false;
}


/*
 ==============================================================================

 CLIENT MESSAGE PARSING

 ==============================================================================
*/


/*
 ==================
 SV_ParseCommand
 ==================
*/
static bool SV_ParseCommand (client_t *client, msg_t *msg){

	char	command[MAX_STRING_LENGTH];
	int		sequence;
	bool	allowGameCommand;

	// Parse the command
	sequence = MSG_ReadLong(msg);
	MSG_ReadString(msg, command, sizeof(command));

	// See if we have already executed it
	if (client->clientCommandSequence >= sequence)
		return true;

	// Drop the connection if we have somehow lost commands
	if (sequence > client->clientCommandSequence + 1){
		SV_DropClient(client, Str_VarArgs("Lost %i reliable commands", sequence - client->clientCommandSequence + 1));
		return false;
	}

	client->clientCommandSequence = sequence;

	// Malicious users may try using too many string commands to lag other
	// players. If we decide that we want to stall the command, we will stop
	// processing the rest of the packet, including the user commands. This
	// causes flooders to lag themselves but not other people.
	// We don't do this when the client hasn't been active yet since it's
	// normal to spam a lot of commands when connecting.
	if (client->state != CS_ACTIVE)
		allowGameCommand = true;
	else {
		if (!sv_floodProtect->integerValue || svs.time >= client->clientCommandTime)
			allowGameCommand = true;
		else
			allowGameCommand = false;
	}

	// Don't allow another command for one second
	client->clientCommandTime = svs.time + 1000;

	// Execute it
	return SV_ExecuteClientCommand(client, command, allowGameCommand);
}

/*
 ==================
 SV_ParseMove
 ==================
*/
static void SV_ParseMove (client_t *client, msg_t *msg, int deltaMessage){

	userCmd_t	nullUserCmd, userCmds[USERCMD_BACKUP];
	userCmd_t	*oldUserCmd, *newUserCmd;
	int			index, count;
	int			i;

	// Set the delta message
	client->deltaMessage = deltaMessage;

	// Parse the user command count
	count = MSG_ReadByte(msg);

	if (count < 1)
		return;

	if (count > USERCMD_BACKUP)
		count = USERCMD_BACKUP;

	// Parse all the user commands
	oldUserCmd = &nullUserCmd;
	Mem_Fill(oldUserCmd, 0, sizeof(userCmd_t));

	for (i = 0; i < count; i++){
		newUserCmd = &userCmds[i];

		if (MSG_ReadBits(msg, 1))
			newUserCmd->time = oldUserCmd->time + MSG_ReadBits(msg, 8);
		else
			newUserCmd->time = MSG_ReadBits(msg, 32);

		newUserCmd->flags = MSG_ReadDeltaByte(msg, oldUserCmd->flags);
		newUserCmd->buttons = MSG_ReadDeltaLong(msg, oldUserCmd->buttons);
		newUserCmd->forwardMove = MSG_ReadDeltaChar(msg, oldUserCmd->forwardMove);
		newUserCmd->sideMove = MSG_ReadDeltaChar(msg, oldUserCmd->sideMove);
		newUserCmd->upMove = MSG_ReadDeltaChar(msg, oldUserCmd->upMove);
		newUserCmd->angles[0] = MSG_ReadDeltaShort(msg, oldUserCmd->angles[0]);
		newUserCmd->angles[1] = MSG_ReadDeltaShort(msg, oldUserCmd->angles[1]);
		newUserCmd->angles[2] = MSG_ReadDeltaShort(msg, oldUserCmd->angles[2]);

		oldUserCmd = newUserCmd;
	}

	// Align to the next byte boundary
	MSG_ReadAlign(msg);

	// Record information about this packet
	index = client->messageAcknowledge & PACKET_MASK;

	client->packets[index].timeAcked = svs.time;

	// Ignore it if we're getting remaining parasite user commands
	if (client->serverId != sv.serverId){
		Com_DPrintf(S_COLOR_YELLOW "Ignoring outdated user command from %s\n", client->playerName);
		return;
	}

	// Drop the connection if the client's pack file checksums haven't been
	// verified
	if (!client->pureAuthentic){
		SV_DropClient(client, "Sent a user command without prior pure verification");
		return;
	}

	// If this is the first user command we have received this server state,
	// put the client into the game
	if (client->state == CS_PRIMED){
		Com_DPrintf("Going to CS_ACTIVE for %s\n", client->playerName);

		client->state = CS_ACTIVE;

		client->serverStateMessage = -1;

		client->gameStateMessage = -1;
		client->gameStateOffset = -1;

		client->deltaMessage = -1;

		client->lastUserCmd = userCmds[0];

		client->nextPacketTime = svs.time;			// Send a packet immediately

		// Call the begin function
		glqSGame->ClientBegin(client->clientNum);
	}

	// Usually, the first couple user commands will be duplicates of ones we
	// have previously received, but the times in the user commands will cause
	// them to be immediately discarded
	for (i = 0; i < count; i++){
		// Don't execute if this is an old user command which is already
		// executed
		if (userCmds[i].time <= client->lastUserCmd.time)
			continue;

		client->lastUserCmd = userCmds[i];

		// Execute it
		glqSGame->ClientThink(client->clientNum, &client->lastUserCmd);

		if (client->state != CS_ACTIVE)
			return;		// May have been dropped during the last user command
	}
}

/*
 ==================
 
 ==================
*/
static void SV_ParseVoice (client_t *client, msg_t *msg, bool echo){

}

/*
 ==================

 ==================
*/
void SV_ParseClientMessage (client_t *client, msg_t *msg){

	bool	allowCommands = true;
	int		op;

	MSG_ClearReadState(msg);

	// Skip past the sequence number
	MSG_ReadLong(msg);

	// Parse the server id
	client->serverId = MSG_ReadLong(msg);

	// Parse the message acknowledge
	client->messageAcknowledge = MSG_ReadLong(msg);

	if (client->messageAcknowledge < 0){
		Com_DPrintf(S_COLOR_YELLOW "Bad message acknowledge from %s\n", client->playerName);
		return;
	}

	// Parse the reliable acknowledge
	client->reliableCommandAcknowledge = MSG_ReadLong(msg);

	if (client->reliableCommandAcknowledge < client->reliableCommandSequence - MAX_RELIABLE_COMMANDS){
		client->reliableCommandAcknowledge = client->reliableCommandSequence;

		Com_DPrintf(S_COLOR_YELLOW "Bad reliable command acknowledge from %s\n", client->playerName);
		return;
	}

	// If this is a message from a previous server state, ignore it and
	// retransmit the current server state.
	// If the client is downloading, let it stay at whatever server id and
	// server state it was at. This allows it to keep downloading even when the
	// server state changes. After the download is finished, we'll notice and
	// send it a new server state.
	if (client->serverId != sv.serverId && !client->downloadChecksum)
		return;

	// Parse the message
	while (1){
		if (msg->readCount > msg->writeCount){
			Com_DPrintf(S_COLOR_YELLOW "Read past end of client message from %s\n", client->playerName);
			return;
		}

		// Parse the client op
		op = MSG_ReadByte(msg);

		if (op == CLC_EOF)
			return;

		if (op != CLC_COMMAND)
			allowCommands = false;

		switch (op){
		case CLC_NOP:

			break;
		case CLC_COMMAND:
			if (!allowCommands)
				return;		// We can't execute it because a move was issued

			if (!SV_ParseCommand(client, msg))
				return;		// We couldn't execute it because of the flood protection

			break;
		case CLC_MOVE:
			SV_ParseMove(client, msg, client->messageAcknowledge);
			break;
		case CLC_MOVENODELTA:
			SV_ParseMove(client, msg, -1);
			break;
		case CLC_VOICE:
			SV_ParseVoice(client, msg, false);
			break;
		case CLC_VOICEECHO:
			SV_ParseVoice(client, msg, true);
			break;
		default:
			Com_DPrintf(S_COLOR_YELLOW "Illegible client message from %s\n", client->playerName);
			return;
		}

		if (client->state == CS_ZOMBIE)
			return;		// Dropped
	}
}